package com.cskaoyan.javase.object._4hashcode;
/**
 * Object类当中的hashCode方法
 * 1.方法的声明
 *     public native int hashCode();
 * 它是一个本地方法,但是它和getClass方法不同的是,它没有final修饰,所以它可以被重写
 * 其他的,该方法不需要参数,有一个int类型的返回值,方法的结果是一个int整数.
 *
 * 2.方法的作用:
 *      1.和方法名一样,该方法的作用是获取哈希值.
 *      2.支持此方法是为了提高哈希表（例如 java.util.Hashtable 提供的哈希表）的性能。
 *
 *  Java中的hashCode方法表示一种哈希映射的规则,它把一个无限大小的集合(某个类的对象)
 *      映射到一个有限大小的集合(int整数)
 *      这就是Java中的哈希映射:就是把一个对象变成一个int整数,这个int整数就是该对象的哈希值
 *      在这个过程中,可能会出现"多对一"的情况,称之为"哈希冲突".
 *
 *  显然哈希冲突是越少越好的,哈希冲突越少,哈希表中需要存储在数组下面(给"挂起""的元素)就越少
 *  哈希表的性能就越好
 *  这主要依赖于优秀的hash算法的设计.
 *
 * 3.Java中的哈希算法,其实就是hashCode方法的实现
 *      首先我们看一下默认的Object类当中的哈希算法实现:
 *      由于它是一个本地方法,查看文档得知:
 *      "实际上，由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。（这一般是通过将该对象的内部地址转换成一个整数来实现的，但是 JavaTM 编程语言不需要这种实现技巧。） "
 *      所以默认的Object类当中的哈希算法,是根据对象的地址计算出一个十进制int整数.
 *      如果对象的地址一样(同一个对象)那么它们的哈希值是一定相同的(因为映射的定义告诉我们,集合A唯一映射集合B)
 *      反之,如果不是同一个对象,一般来说哈希值是不同的.(但是出现相同的情况,也很正常,因为哈希冲突)
 *
 * 4.很多时候,我们不满于Object类当中的默认实现,需要重写hashCode方法
 *      重写时,也有必须要遵守的规则:
 *      hashCode 的常规协定是：
 *
 *          1.在 Java 应用程序执行期间，在对同一对象多次调用 hashCode 方法时，必须一致地返回相同的整数
 *              前提是将对象进行 equals 比较时所用的信息没有被修改。
 *              hashCode方法和equals关系很紧密,
 *                  只要equals方法比较对象相等时的依据没有更改,那么它们的哈希值就一定相等.
 *              只要equals方法比较对象相等时的依据没有更改 --> 之前对象相等,不改比较依据,对象还相等
 *              ---> 对象既然相等,这意味着它们是集合A中的同一个元素,那么哈希值必须也是一样的
 *
 *          2.从某一应用程序的一次执行到同一应用程序的另一次执行，该整数无需保持一致。
 *              因为在程序的另一次执行过程中,equals 比较时所用的信息可能会更改.
 *
 *          3.如果根据 equals(Object) 方法，两个对象是相等的.
 *              那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
 *              equals方法在这两个方法中是"老大"的地位 -->
 *              如果equals方法认为对象相等,这意味着它们是集合A中的同一个元素,那么哈希值必须也是一样的
 *              equals方法和hashCode方法必须保持"同true同哈希值"
 *
 *              思考: equals方法和hashCode方法能不能单独重写?
 *              equals方法和hashCode方法要么都不重写,要么必须都同时重写(而且重写的依据必须一样).
 *                  比如equals方法使用成员变量age和score重写
 *                  那么hashCode也必须用这两个成员变量重写,这样才能保证"同true同哈希值"
 *
 *                  因为如果都不重写的话,它们都是依据地址来计算.
 *
 *
 *          4.如果根据 equals(java.lang.Object) 方法，两个对象不相等，
 *          那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。
 *          --> 对象不相等,说明不是集合A中的同一个对象.而映射不强制要求决对"一一对应","多对一"是很正常的
 *              也就是说哈希冲突是一定会存在的
 *
 *          但是，程序员应该意识到，为不相等的对象生成不同整数结果可以提高哈希表的性能。
 *              哈希冲突越少,哈希表性能越好.
 *
 *
 *  5.到底怎么重写hashCode方法呢?
 *      和equals方法保证一样的重写规则:使用相同的成员变量的取值重写.
 *      一般来说,使用IDEA自动生成就可以了.如果有特殊需求,可以自己重写.
 *
 *
 * 细节问题:
 *      1.toString方法的默认实现,会调用该对象的hashCode方法.
 *          默认的hashCode方法实现是通过计算地址得到的一个值
 *          但是如果重写过hashCode方法,尤其是通过成员变量的取值重写,那么默认的toString方法就不会再打印地址值了.
 *          所以如果重写了hashCode方法,可以顺手一起重写toString方法
 *      2.如果类中有引用数据类型成员变量,建议一起重写该类的hashCode方法
 *
 * @since 14:32
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18,100);
        Student s2 = new Student(18,100);
        // Student s3 = s1;
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        // System.out.println(s3.hashCode());
        Student s3 = new Student(19,150);
        System.out.println(s3.hashCode());
        System.out.println(s1);
        System.out.println(s2);
    }
}
class Student{
    int age;
    double score;
    Star s;


    public Student() {
    }

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;
        return s != null ? s.equals(student.s) : student.s == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + (s != null ? s.hashCode() : 0);
        return result;
    }
}
class Star{
    double salary;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Star star = (Star) o;

        return Double.compare(star.salary, salary) == 0;
    }

    @Override
    public int hashCode() {
        long temp = Double.doubleToLongBits(salary);
        return (int) (temp ^ (temp >>> 32));
    }
}
